:- module(fd_sets).
:- export watch / 1.
:- export compare_instances_set / 3.
:- export copy_term_set / 2.
:- export delayed_goals_number_set / 2.
:- export suspensions_set / 3.
:- export test_unify_sets / 2.
:- export unify_sets / 3.
:- export print_setvar / 2.
:- export set_range / 3.
:- export potential_members / 2.
:- export is_solver_type / 1.
:- export is_solver_var / 1.
:- export get_set_attribute / 2.
:- export insetdomain / 4.
:- export membership_booleans / 2.
:- export weight / 3.
:- export # / 2.
:- export symdiff / 3.
:- export difference / 3.
:- export intersection / 3.
:- export union / 3.
:- export subset / 2.
:- export includes / 2.
:- export all_intersection / 2.
:- export all_union / 2.
:- export all_disjoint / 1.
:- export sameset / 2.
:- export disjoint / 2.
:- export notin / 2.
:- export in / 3.
:- export in / 2.
:- export intsets / 4.
:- export intset / 3.
:- export (::) / 2.
:- export op(500, yfx, \).
:- export op(700, xfx, disjoint).
:- export op(700, xfx, sameset).
:- export op(700, xfx, in).
:- export op(700, xfx, notin).
:- export op(700, xfx, includes).
:- export op(700, xfx, subset).
:- export struct(int_sets(dom, off, lcard, ucard, added, removed, add, rem, card, booleans, value)).
:- comment(struct(int_sets), [summary : "Attribute structure for set variables (and constants)", fields : ["dom" : "set domain representation (array of booleans)", "off" : "offset between set element and corresponding array index (integer)", "lcard" : "lower bound cardinality (integer)", "ucard" : "upper bound cardinality (integer)", "added" : "notification send-port for lower bound increases", "removed" : "notification send-port for upper bound decreases", "add" : "suspension list woken when lower bound increases", "rem" : "suspension list woken when upper bound decreases", "card" : "fd/ic variable for cardinality (see #/2)", "booleans" : "array of fd/ic variables (see membership_booleans/2)", "value" : "set variable belonging to this attribute"], see_also : [library(notify_ports), suspend / 3, suspend / 4]]).
:- comment(author, "Joachim Schimpf, Neng-Fa Zhou").
:- comment(date, "$Date: 2004/11/17 20:13:57 $").
:- comment(copyright, "Imperial College London and Parc Technologies").
:- comment(desc, html("\n    This is a solver for constraints over the domain of finite integer sets.\n    </P><P>\n    (Ground) integer sets are represented simply as sorted, duplicate-free\n    lists of integers e.g.\n    <PRE>\n    \tSetOfThree = [1,3,7]\n    \tEmptySet = []\n    </PRE>\n\n<H3>Set Variables</H3>\n    Set variables are variables which can eventually take a ground integer\n    set as their value. They are characterized by a lower bound (the set \n    of elements that are definitely in the set) and an upper bound (the\n    set of elements that may be in the set). A set variable can be declared\n    as follows:\n    <PRE>\n    \tSetVar :: []..[1,2,3,4,5,6,7],\n    </PRE>\n    Since the lower bound is the empty set, this can be written as\n    <PRE>\n    \tSetVar subset [1,2,3,4,5,6,7],\n    </PRE>\n    If the lower bound is the empty set and the upper bound is a set\n    of consecutive integers, you can also write\n    <PRE>\n    \tintset(SetVar, 1, 7)\n    </PRE>\n\n<H3>Set Constraints</H3>\n    Most of the usual set operations/relations are provided as constraints:\n    <UL>\n    <LI>membership\n    <LI>non-membership\n    <LI>inclusion (subset)\n    <LI>equality\n    <LI>intersection\n    <LI>union\n    <LI>difference\n    <LI>symmetric difference\n    <LI>disjointness\n    <LI>cardinality\n    </UL>\n    as well as a constraint on set weight.  Note that there is no\n    complement-constraint because the library has no concept of a set\n    universe and cannot represent infinite sets.\n    </P><P>\n    On most argument positions where sets are expected, set expressions\n    are allowed, e.g.\n    <PRE>\n    Set1 /\\ Set2       % intersection\n    Set1 \\/ Set2       % union\n    Set1 \\ Set2        % difference\n    </PRE>\n    </P>\n\n<H3>Search</H3>\n    The insetdomain/4 predicate can be used to enumerate all ground\n    instantiations of a set variable, much like indomain/1 in the\n    finite-domain case.\n\n<H3>Cooperation with a finite domain solver</H3>\n    This library comes in two flavours: lib(fd_sets) which cooperates with\n    lib(fd), and lib(ic_sets) which cooperates with lib(ic). This is relevant\n    only for those constraints which involve integer variables, e.g. the\n    cardinality argument in #/2, the weight argument in weight/3 and the\n    booleans in membership_booleans/2. These will be represented as fd-\n    or ic-variables respectively.\n")).
:- comment(summary, "Solver over sets of integers (cooperates with lib(fd))").
:- comment(eg, "\n% Example program: Steiner triplets\n% Compute NB triplets of numbers from 1 to N such that\n% any two triplets have at most one element in common.\n% Try steiner(9,Sets).\n\n:- lib(fd_sets).\n:- lib(fd).\n\nsteiner(N, Sets) :-\n\tNB is N * (N-1) // 6,\t\t% compute number of triplets\n\tintsets(Sets, NB, 1, N),\t% initialise the set variables\n\t( foreach(S,Sets) do\n\t    #(S,3)\t\t\t% constrain their cardinality\n\t),\n\t( fromto(Sets,[S1|Ss],Ss,[]) do\n\t    ( foreach(S2,Ss), param(S1) do\n\t\t#(S1 /\\ S2, C),\t\t% constrain the cardinality\n\t\tC #=< 1\t\t\t% of pairwise intersections\n\t    )\n\t),\n        label_sets(Sets).\t\t% search\n\nlabel_sets([]).\nlabel_sets([S|Ss]) :-\n        insetdomain(S,_,_,_),\n\tlabel_sets(Ss).\n").
:- comment(# / 2, [summary : "Card is the cardinality of the integer set Set", exceptions : [4 : "Set is a variable, but not a set variable"], amode : #(?, ?), args : ["Set" : "a set, set variable or set expression", "Card" : "a variable, finite domain variable, or integer"], desc : html("<P>\n    This is a constraint for the cardinality of the set Set.\n    Any changes in the potential cardinality of Set will be reflected\n    in the finite domain of Card. Restricting the domain of Card will\n    restrict the potential cardinality of Set.\n</P>")]).
:- comment((::) / 2, [summary : "Set is an integer set within the given bounds", template : "?Set :: ++Lwb..++Upb", args : ["Set" : "A free variable, set variable or an integer list", "Lwb..Upb" : "Structure holding two lists of integers"], fail_if : "Lwb is not a sublist of Upb", exceptions : [5 : "Set is not a variable or list, or Lwb..Upb is not a ../2 structure"], see_also : [intset / 3, intsets / 4], desc : html("<P>\n    Lwb and Upb are two lists of integers. Lwb must be a sublist of\n    Upb. Set is unified with a set variable whose lower bound is the\n    set of list elements of Lwb, and whose upper bound is the set of\n    list elements of Upb.\n</P>")]).
:- comment(all_disjoint / 1, [summary : "Sets is a list of integers sets which are all disjoint", template : "all_disjoint(+Sets)"]).
:- comment(all_intersection / 2, [summary : "Intersection is the intersection of all the sets in the list Sets", amode : all_intersection(+, ?), args : ["Sets" : "a non-empty list of sets, set variables or set expressions", "Intersection" : "a set, set variable, free variable or set expression"]]).
:- comment(all_union / 2, [summary : "SetUnion is the union of all the sets in the list Sets", amode : all_union(+, ?), args : ["Sets" : "a list of sets, set variables or set expressions", "SetUnion" : "a set, set variable, free variable or set expression"]]).
:- comment(difference / 3, [summary : "Set3 is the difference of the integer sets Set1 and Set2", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable or set expression", "Set3" : "a set, set variable, free variable or set expression"], template : "difference(?Set1, ?Set2, ?Set3)"]).
:- comment(disjoint / 2, [summary : "The integer sets Set1 and Set2 are disjoint", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable or set expression"], template : "?Set1 disjoint ?Set2"]).
:- comment(get_set_attribute / 2, [summary : "Get the set-attribute corresponding to Set", args : ["Set" : "A ground set or a set variable", "Attr" : "A variable, will be bound to a set-attribute structure"], amode : get_set_attribute(?, -), desc : html("<P>\n    Gets the set-attribute of a set-variable (or computes an attribute\n    structure describing a ground set as if it was a variable).\n</P>")]).
:- comment(in / 2, [summary : "The integer X is member of the integer set Set", args : ["X" : "an integer or an (integer) variable", "Set" : "a set, set variable, or set expression"], template : "?X in ?Set"]).
:- comment(in / 3, [summary : "Reified version of the set membership constraint", args : ["X" : "an integer or an (integer) variable", "Set" : "a set, set variable, or set expression", "Bool" : "0, 1 or a boolean variable"], see_also : [in / 2, notin / 2, membership_booleans / 2], exceptions : [4 : "Set is a free variable without set domain"], desc : html("<P>\n    This is the reified version of the in/2 constraint. This means that\n    the boolean variable Bool reflects the truth of the relation <EM>X in Set</EM>.\n    If Bool is 1, then in(X,Set,Bool) is the same as <EM>X in Set</EM>.\n    If Bool is 0, then in(X,Set,Bool) is the same as <EM>X notin Set</EM>.\n    Otherwise, Bool will be bound to 0 or 1 when <EM>X in Set</EM>\n    is known to be false or true, respectively. The latter is only\n    guaranteed to be detected after X has become instantiated.\n</P><P>\n    Note that if one wants to have booleans corresponding to all or most\n    of the set domain elements, it will be more efficient to use the\n    membership_booleans/2 constraint in place of many in/3 constraints.\n</P>"), eg : "\n    ?- intset(S, 1, 9), in(E, S, B).\n    E = E{[-10000000 .. 10000000]}\n    S = S{([] .. [1, 2, 3, 4, 5, 6, 7, 8, 9]) : C{[0 .. 9]}}\n    B = B{[0, 1]}\n    There is 1 delayed goal.\n\n    ?- intset(S, 1, 9), in(E, S, B), B = 1.\n    E = E{[1 .. 9]}\n    S = S{([] .. [1, 2, 3, 4, 5, 6, 7, 8, 9]) : C{[0 .. 9]}}\n    B = 1\n    There is 1 delayed goal.\n\n    ?- intset(S, 1, 9), in(E, S, B), E = 3, B = 1.\n    S = S{[3] \\/ ([] .. [1, 2, 4, 5, 6, 7, 8, 9]) : C{[1 .. 9]}}\n    E = 3\n    B = 1\n\n    ?- intset(S, 1, 9), in(E, S, B), E = 3, B = 0.\n    S = S{([] .. [1, 2, 4, 5, 6, 7, 8, 9]) : C{[0 .. 8]}}\n    E = 3\n    B = 0\n    Yes (0.00s cpu)\n\n    ?- intset(S, 1, 9), in(E, S, B), E = 3, S includes [2, 3, 4].\n    B = 1\n    E = 3\n    S = S{[2, 3, 4] \\/ ([] .. [1, 5, 6, 7, 8, 9]) : C{[3 .. 9]}}\n    There is 1 delayed goal.\n\n    ?- intset(S, 1, 9), in(E, S, B), E = 3, S disjoint [2, 3, 4].\n    B = 0\n    E = 3\n    S = S{([] .. [1, 5, 6, 7, 8, 9]) : C{[0 .. 6]}}\n    There is 1 delayed goal.\n\n    ?- in(3, S, 1).\n    instantiation fault\n    "]).
:- comment(includes / 2, [summary : "Set1 includes (is a superset of) the integer set Set2", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable, free variable or set expression"], template : "?Set1 includes ?Set2"]).
:- comment(insetdomain / 4, [summary : "Instantiate Set to a possible value", amode : insetdomain(?, ?, ?, ?), args : ["Set" : "a set or set variable", "CardSel" : "atom or variable", "ElemSel" : "atom, structure or variable", "Order" : "atom or variable"], resat : yes, exceptions : [4 : "Set is a variable, but not a set variable"], see_also : [refine / 1, potential_members / 2, sbds_try / 2, sbds_initialise / 4, sbds_initialise / 5, sbds_try_set / 3, library(ic_gap_sbds), sbdd_try_set / 3, library(ic_gap_sbdd)], desc : html("<P>\n    This predicate instantiates a set variable to a possible value,\n    according to its domain.  The predicate backtracks over all\n    possible set instantiations.  The three option arguments allow to\n    choose between a number of different enumeration orders. Giving a\n    variable as option argument will select the default.\n</P><P>\n    The <B>CardSel</B> argument determines whether the sets are enumerated\n    according to their cardinality. It can take the following values:\n<DL>\n<DT><B>any</B> (default)</DT>\n    <DD>the sets are not enumerated in a particular cardinality order</DD>\n<DT><B>increasing</B></DT>\n    <DD>the sets are enumerated with increasing cardinality, ie. small\n    sets are tried first</DD>\n<DT><B>decreasing</B></DT>\n    <DD>the sets are enumerated with decreasing cardinality, ie. large\n    sets are tried first</DD>\n</DL>\n</P><P>\n    The <B>ElemSel</B> argument determines which potential set elements\n    are considered first for inclusion or exclusion. It can take the\n    following values:\n<DL>\n<DT><B>small_first</B> (default)</DT>\n    <DD>small set elements (small numbers) are considered first</DD>\n<DT><B>big_first</B></DT>\n    <DD>big set elements (big numbers) are considered first</DD>\n<DT><B>random</B></DT>\n    <DD>potential set elements are considered in random order</DD>\n<DT><B>heavy_first(Weights)</B></DT>\n    <DD>heavy set elements (according to Weight array) are considered first</DD>\n<DT><B>light_first(Weights)</B></DT>\n    <DD>light set elements (according to Weight array) are considered first</DD>\n</DL>\n</P><P>\n    The <B>Order</B> argument determines whether it is first tried to make\n    the selected potential element a set member, or whether to exclude it\n    first.  The argument can take the following values:\n<DL>\n<DT><B>in_notin</B> (default)</DT>\n    <DD>try inclusion first, then exclusion</DD>\n<DT><B>notin_in</B></DT>\n    <DD>try exclusion first, then inclusion</DD>\n<DT><B>sbds</B></DT>\n    <DD>uses sbds_try/2 to include or exclude an element in or from a set\n    (this is for use in conjunction with the classic SBDS library\n    (lib(ic_sbds) or lib(fd_sbds)), and whether inclusion or exclusion is\n    tried first depends on the \"fix pred\" specified in the prior call to\n    sbds_initialise/4 or sbds_initialise/5)</DD>\n<DT><B>gap_sbds_in_notin</B></DT>\n    <DD>try inclusion first, then exclusion, using sbds_try_set/3 (this is\n    for use in conjunction with the GAP-based SBDS library lib(ic_gap_sbds))</DD>\n<DT><B>gap_sbds_notin_in</B></DT>\n    <DD>try exclusion first, then inclusion, using sbds_try_set/3 (this is\n    for use in conjunction with the GAP-based SBDS library lib(ic_gap_sbds))</DD>\n<DT><B>gap_sbdd_in_notin</B></DT>\n    <DD>try inclusion first, then exclusion, using sbdd_try_set/3 (this is\n    for use in conjunction with the GAP-based SBDD library lib(ic_gap_sbdd))</DD>\n<DT><B>gap_sbdd_notin_in</B></DT>\n    <DD>try exclusion first, then inclusion, using sbdd_try_set/3 (this is\n    for use in conjunction with the GAP-based SBDD library lib(ic_gap_sbdd))</DD>\n</DL>\n</P><P>\n    Note that there are many different enumeration strategies for a set\n    variable, and insetdomain/4 only allows a limited number of them.\n    For an actual application, it might be more appropriate to choose a\n    problem-specific enumeration order. This can be programmed easily.\n    As a guideline, here is the code for insetdomain with the default\n    options:\n<PRE>\n    insetdomain(Set, _, _, _) :-\n    \tnonvar(Set).\n    insetdomain(Set, any, small_first, in_notin) :-\n    \tvar(Set),\n    \tpotential_members(Set, PotentialElements),\n\tPotentialElements = [Element|_],\n\t(\n\t    Element in Set\n\t;\n\t    Element notin Set\n\t),\n\tinsetdomain(Set, any, small_first, in_notin).\n</PRE>\n"), eg : "\n?-  X::[]..[1,2,3], insetdomain(X,_,_,_), writeln(X), fail.\n[1, 2, 3]\n[1, 2]\n[1, 3]\n[1]\n[2, 3]\n[2]\n[3]\n[]\n\nno (more) solution.\n?-  X::[]..[1,2,3], insetdomain(X,increasing,_,_), writeln(X), fail.\n[]\n[1]\n[2]\n[3]\n[1, 2]\n[1, 3]\n[2, 3]\n[1, 2, 3]\n\nno (more) solution.\n?-  X::[]..[1,2,3], insetdomain(X,_,big_first,_), writeln(X), fail.\n[1, 2, 3]\n[2, 3]\n[1, 3]\n[3]\n[1, 2]\n[2]\n[1]\n[]\n\nno (more) solution.\n?-  X::[]..[1,2,3], insetdomain(X,_,_,notin_in), writeln(X), fail.\n[]\n[3]\n[2]\n[2, 3]\n[1]\n[1, 3]\n[1, 2]\n[1, 2, 3]\n\nno (more) solution.\n?-  X::[]..[1,2,3],\n\tinsetdomain(X, increasing, heavy_first([](2,9,4,7)), _),\n\twriteln(X), fail.\n[]\n[2]\n[3]\n[1]\n[2, 3]\n[1, 2]\n[1, 3]\n[1, 2, 3]\n\nno (more) solution.\n"]).
:- comment(intersection / 3, [summary : "Set3 is the intersection of the integer sets Set1 and Set2", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable or set expression", "Set3" : "a set, set variable, free variable or set expression"], template : "intersection(?Set1, ?Set2, ?Set3)"]).
:- comment(intset / 3, [summary : "Set is a set containing numbers between Min and Max", exceptions : [4 : "Min or Max are uninstantiated", 5 : "Set is not a variable or list"], args : ["Set" : "A free variable, set variable or an integer list", "Min" : "integer", "Max" : "integer"], see_also : [intsets / 4, (::) / 2], amode : intset(?, +, +), desc : html("<P>\n    Unifies Set with a set variable whose lower bound is the empty set\n    and whose upper bound is the set of all integers between Min and Max.\n    Equivalent to Set::[]..[Min,Min+1,..,Max].\n</P>")]).
:- comment(intsets / 4, [summary : "Sets is a list of N sets containing numbers between Min and Max", exceptions : [4 : "Min or Max are uninstantiated"], see_also : [intset / 3, (::) / 2], template : "intsets(?Sets, ?N, +Min, +Max)"]).
:- comment(is_solver_type / 1, [summary : "Succeeds if Term is a ground set or a set variable", args : ["Term" : "A term"], fail_if : "Set is neither a set nor a set variable", desc : html("<P>\n    Succeeds if Term is either a set (represented as a list) or a set\n    variable. For efficiency reasons, lists are not checked for being\n    valid set representations (i.e. strictly sorted lists of integers),\n    the predicate succeeds for any list.\n</P>")]).
:- comment(is_solver_var / 1, [summary : "Succeeds if Term is a set variable", args : ["Term" : "A term"], fail_if : "Set is not a variable, or a non-set variable"]).
:- comment(membership_booleans / 2, [summary : "BoolArr is an array of booleans describing Set", amode : membership_booleans(?, ?), args : ["Set" : "a set, set variable, free variable or set expression", "BoolArr" : "A variable or an array (structure) of boolean variables"], desc : html("<P>\n    This constraint maintains the correspondence between a finite set and\n    an array of booleans (0/1 variables). The set is constrained to contain\n    those (and only those) integers I between 1 and N (the size of the array)\n    where the corresponding array element is 1.\n</P><P>\n    Operationally, setting a array element I to 0 will exclude the element\n    I from Set's domain. Setting I to 1 will include I in Set's lower bound.\n    Adding and removing elements from Set will be reflected in the\n    corresponding boolean being instantiated to 1 or 0 respectively.\n</P><P>\n    When called with BoolArr being a free variable, an array will be created\n    whose arity is at least as big as the largest potential set member.\n    Set members smaller than 1 will be excluded from the set.\n    When called with BoolArr being instantiated to an array, set members\n    smaller than 1 or bigger than the array size will be excluded from\n    the set.\n</P>"), eg : "\n    ?- S::[2,4]..[1,2,3,4,5], membership_booleans(S,B).\n\n    S = S{[2, 4] \\/ ([] .. [1, 3, 5]) : _{[2 .. 5]}}\n    B = [](_{[0, 1]}, 1, _{[0, 1]}, 1, _{[0, 1]})\n\n    Delayed goals:\n\t...\n\n\n    ?- membership_booleans(S, [](0,1,0,1,B5)).\n\n    S = S{[2, 4] \\/ ([] .. [5]) : _{[2, 3]}}\n    B5 = B5{[0, 1]}\n\n    Delayed goals:\n\t...\n    "]).
:- comment(notin / 2, [summary : "The integer X is not a member of the integer set Set", args : ["X" : "an integer or an (integer) variable", "Set" : "a set, set variable, or set expression"], template : "?X notin ?Set"]).
:- comment(potential_members / 2, [summary : "List is the list of elements of whose membership in Set is currently uncertain", exceptions : [4 : "Set is a variable, but not a set variable"], amode : potential_members(?, -), args : ["Set" : "a set or set variable", "List" : "variable, will be unified with a list"], eg : "\n    \t?- S :: [2,4]..[1,2,3,4,5], potential_members(S, P).\n\tS = S{[2, 4] \\/ ([] .. [1, 3, 5]) : _{[2 .. 5]}}\n\tP = [1, 3, 5]\n    "]).
:- comment(sameset / 2, [summary : "The sets Set1 and Set2 are equal", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable or set expression"], template : "?Set1 sameset ?Set2"]).
:- comment(set_range / 3, [summary : "Lwb and Upb are the current lower and upper bounds on Set", amode : set_range(?, -, -), args : ["Set" : "A variable or an integer list", "Lwb" : "List of integers", "Upb" : "List of integers"], fail_if : "Set is a variable, but not a set variable", desc : html("<P>\n    Returns two sorted, duplicate-free lists of integers which represent\n    the lower and upper bound of the set variable (or the ground set) Set.\n    The predicate can also be used as a test for set-variables, since it\n    fails for any other (in particular domain-less) variables.\n</P>")]).
:- comment(subset / 2, [summary : "Set1 is a (non-strict) subset of the integer set Set2", args : ["Set1" : "a set, set variable, free variable or set expression", "Set2" : "a set, set variable or set expression"], template : "?Set1 subset ?Set2"]).
:- comment(symdiff / 3, [summary : "Set3 is the symmetric difference of the integer sets Set1 and Set2", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable or set expression", "Set3" : "a set, set variable, free variable or set expression"], template : "symdiff(?Set1, ?Set2, ?Set3)"]).
:- comment(union / 3, [summary : "Set3 is the union of the integer sets Set1 and Set2", args : ["Set1" : "a set, set variable or set expression", "Set2" : "a set, set variable or set expression", "Set3" : "a set, set variable, free variable or set expression"], template : "union(?Set1, ?Set2, ?Set3)"]).
:- comment(weight / 3, [summary : "According to the array of element weights, the weight of set Set is Weight", amode : weight(?, ++, ?), args : ["Set" : "a set, set variable or set expression", "ElementWeights" : "an array (structure) of non-negative integers", "Weight" : "an integer variable or integer"], desc : html("<P>\n    Set is constrained to be a set of integers between 1 and the size of\n    the array ElementWeights. Weight is an integer domain variable which\n    is constrained to the sum of the weights of Set's elements. The weight\n    of a set element is determined by the array element in ElementWeights\n    whose index corresponds to the set element.\n</P><P>\n    Changes to the set domain will affect the domain of Weight. There is currently\n    no reverse propagation. This may change in a future release.\n</P>")]).
